feat: Add PR Dashboard#500
Conversation
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
✅ Deploy Preview for hiero-open-source ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThis pull request implements a complete GitHub bot system that validates pull requests against automation rules, posts standardized dashboard comments with validation results, and manages PR status labels. It includes configuration-driven check definitions, validation logic for commits and issue linking, safe GitHub API wrappers, and two workflows triggered on PR open and update events. ChangesGitHub PR Automation Bot
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/scripts/bot-on-pr-update.js:
- Line 35: The PR update handler calls swapStatusLabel(botContext, allPassed)
without forcing reconciliation, so PRs that lack both status labels won't get
fixed on synchronize/edited events; update the call in
.github/scripts/bot-on-pr-update.js to pass the force flag (e.g.,
swapStatusLabel(botContext, allPassed, true) or the equivalent third argument
expected by swapStatusLabel) so the function runs a forced reconciliation of
labels when handling update events.
In @.github/scripts/helpers/api.js:
- Around line 670-673: runAllChecksAndComment currently awaits
postOrUpdateComment but ignores its success/failure so dashboard upsert failures
are dropped; change runAllChecksAndComment to capture the result/error from
postOrUpdateComment (call to postOrUpdateComment(botContext, marker, body)),
verify its returned success flag or catch and rethrow/provide a failure
indicator, and propagate that failure in the function return (e.g., include an
upsertSuccess/upsertError field or throw) instead of unconditionally returning {
allPassed } so status-label transitions cannot proceed when the dashboard
comment update failed.
- Around line 8-31: The imports are using named imports but the target modules
export only default objects; update the import contracts to import the default
and then destructure the needed symbols (e.g., in .github/scripts/helpers/api.js
import the default from './logger.js', './validation.js', './constants.js', and
'./comments.js' and then destructure getLogger, isSafeSearchToken,
requireObject, LABELS, SKILL_HIERARCHY, ISSUE_STATE, buildBotComment, etc.), and
likewise change .github/scripts/helpers/constants.js to import the default from
'./config-loader.js' and destructure the required config values; alternatively,
change the source modules to export named symbols—whichever you choose, ensure
imports reference default exports (or make corresponding named exports) so
runtime "does not provide an export named" errors are resolved.
In @.github/scripts/helpers/checks.js:
- Around line 118-123: The current logic in the mergeability check treats an
unresolved mergeable state as “no conflicts” (logger.log at the
mergeableResolved branch) which can incorrectly pass PRs; change the behavior in
the function containing mergeableResolved/conflicts so that if mergeableResolved
is false you either set conflicts = true or throw an error instead of assuming
no conflicts, update the logger to reflect the fail-closed decision, and ensure
the returned object from the function (the return { passed: !conflicts } line)
will return passed: false (or never return because you throw) when mergeability
never resolved so the orchestrator will mark it as an error.
In @.github/scripts/helpers/config-loader.js:
- Around line 10-18: DEFAULT_CONFIG_PATH currently uses __dirname in an ESM
module (config-loader.js) which is undefined; replace the __dirname usage by
deriving the directory from import.meta.url (use fileURLToPath + path.dirname
from the 'url' module) and then compute DEFAULT_CONFIG_PATH with that directory;
update the top of the module to import or require fileURLToPath and derive
__dirname-equivalent before defining DEFAULT_CONFIG_PATH so functions/exports
that reference DEFAULT_CONFIG_PATH (the constant DEFAULT_CONFIG_PATH) no longer
throw at startup.
In @.github/workflows/on-pr-update.yml:
- Line 20: Add an actionlint configuration entry to whitelist the self-hosted
runner label used in .github/workflows/on-pr-update.yml (the runs-on value
"hiero-client-sdk-linux-large") so actionlint recognizes it as an intended
self-hosted label; update or create the actionlint config (e.g.,
.github/actionlint.yml or .actionlint.yml) to include
"hiero-client-sdk-linux-large" under allowed runner labels and also add a short
note in CI/runner documentation referencing that label for future maintenance.
In @.github/workflows/on-pr.yml:
- Around line 35-36: The checkout step currently uses actions/checkout without
disabling credential persistence; update the Checkout Repository step (the
actions/checkout@... usage) in both .github/workflows/on-pr.yml and
.github/workflows/on-pr-update.yml to include persist-credentials: false so the
checkout action does not persist the GITHUB_TOKEN across subsequent steps in the
privileged pull_request_target workflow.
- Around line 42-43: Replace the CommonJS require usage that loads the ESM bot
entrypoint (the line using require('./.github/scripts/bot-on-pr-open.js') and
the subsequent await script({ github, context })) with a dynamic import and
invoke the module's default export: use await
import('./.github/scripts/bot-on-pr-open.js') to get the module object and then
call its default export with the same arguments (e.g., await mod.default({
github, context })), ensuring the ESM export default is executed correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f9350694-07be-410d-b4e4-3a5293b59bcf
📒 Files selected for processing (13)
.github/hiero-automation.json.github/scripts/bot-on-pr-open.js.github/scripts/bot-on-pr-update.js.github/scripts/helpers/api.js.github/scripts/helpers/checks.js.github/scripts/helpers/comments.js.github/scripts/helpers/config-loader.js.github/scripts/helpers/constants.js.github/scripts/helpers/index.js.github/scripts/helpers/logger.js.github/scripts/helpers/validation.js.github/workflows/on-pr-update.yml.github/workflows/on-pr.yml
| } | ||
|
|
||
| const { allPassed } = await runAllChecksAndComment(botContext); | ||
| const result = await swapStatusLabel(botContext, allPassed); |
There was a problem hiding this comment.
Force status-label reconciliation on update events.
Line 35 calls swapStatusLabel without force, so PRs missing both status labels won’t recover a status label on synchronize/edited events.
Suggested fix
- const result = await swapStatusLabel(botContext, allPassed);
+ const result = await swapStatusLabel(botContext, allPassed, { force: true });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const result = await swapStatusLabel(botContext, allPassed); | |
| const result = await swapStatusLabel(botContext, allPassed, { force: true }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/bot-on-pr-update.js at line 35, The PR update handler calls
swapStatusLabel(botContext, allPassed) without forcing reconciliation, so PRs
that lack both status labels won't get fixed on synchronize/edited events;
update the call in .github/scripts/bot-on-pr-update.js to pass the force flag
(e.g., swapStatusLabel(botContext, allPassed, true) or the equivalent third
argument expected by swapStatusLabel) so the function runs a forced
reconciliation of labels when handling update events.
| import { getLogger } from './logger.js'; | ||
|
|
||
| import { | ||
| isSafeSearchToken, | ||
| requireObject, | ||
| requireNonEmptyString, | ||
| requirePositiveInt, | ||
| requireSafeUsername, | ||
| } from './validation.js'; | ||
|
|
||
| import { | ||
| LABELS, | ||
| SKILL_HIERARCHY, | ||
| ISSUE_STATE, | ||
| } from './constants.js'; | ||
|
|
||
| import { | ||
| checkDCO, | ||
| checkGPG, | ||
| checkMergeConflict, | ||
| checkIssueLink, | ||
| } from './checks.js'; | ||
|
|
||
| import { buildBotComment } from './comments.js'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Named imports in api.js =="
rg -n "import\\s*\\{[^}]+\\}\\s*from\\s*'\\./(logger|validation|constants|comments)\\.js'" .github/scripts/helpers/api.js
echo "== Export forms in referenced modules =="
for f in .github/scripts/helpers/logger.js \
.github/scripts/helpers/validation.js \
.github/scripts/helpers/constants.js \
.github/scripts/helpers/comments.js \
.github/scripts/helpers/config-loader.js; do
echo "--- $f"
rg -n "^export\\s+" "$f"
done
echo "== constants.js import from config-loader =="
rg -n "from './config-loader\\.js'" .github/scripts/helpers/constants.jsRepository: hiero-ledger/hiero-website
Length of output: 674
Fix default-vs-named import contracts before runtime (api.js + constants.js)
.github/scripts/helpers/api.jsuses named imports from./logger.js,./validation.js,./constants.js, and./comments.js, but each of those modules exports onlydefault { ... }objects—so module-link errors (“does not provide an export named …”) will occur..github/scripts/helpers/constants.jsalso imports named exports from./config-loader.js, whileconfig-loader.jsis default-only, which would fail similarly once theapi.jsimports are corrected.
💡 One consistent fix path (import defaults + destructure)
-import { getLogger } from './logger.js';
+import loggerHelpers from './logger.js';
+const { getLogger } = loggerHelpers;
-import {
+import validationHelpers from './validation.js';
+const {
isSafeSearchToken,
requireObject,
requireNonEmptyString,
requirePositiveInt,
requireSafeUsername,
-} from './validation.js';
+} = validationHelpers;
-import {
+import constants from './constants.js';
+const {
LABELS,
SKILL_HIERARCHY,
ISSUE_STATE,
-} from './constants.js';
+} = constants;
-import { buildBotComment } from './comments.js';
+import commentHelpers from './comments.js';
+const { buildBotComment } = commentHelpers;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { getLogger } from './logger.js'; | |
| import { | |
| isSafeSearchToken, | |
| requireObject, | |
| requireNonEmptyString, | |
| requirePositiveInt, | |
| requireSafeUsername, | |
| } from './validation.js'; | |
| import { | |
| LABELS, | |
| SKILL_HIERARCHY, | |
| ISSUE_STATE, | |
| } from './constants.js'; | |
| import { | |
| checkDCO, | |
| checkGPG, | |
| checkMergeConflict, | |
| checkIssueLink, | |
| } from './checks.js'; | |
| import { buildBotComment } from './comments.js'; | |
| import loggerHelpers from './logger.js'; | |
| const { getLogger } = loggerHelpers; | |
| import validationHelpers from './validation.js'; | |
| const { | |
| isSafeSearchToken, | |
| requireObject, | |
| requireNonEmptyString, | |
| requirePositiveInt, | |
| requireSafeUsername, | |
| } = validationHelpers; | |
| import constants from './constants.js'; | |
| const { | |
| LABELS, | |
| SKILL_HIERARCHY, | |
| ISSUE_STATE, | |
| } = constants; | |
| import { | |
| checkDCO, | |
| checkGPG, | |
| checkMergeConflict, | |
| checkIssueLink, | |
| } from './checks.js'; | |
| import commentHelpers from './comments.js'; | |
| const { buildBotComment } = commentHelpers; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/helpers/api.js around lines 8 - 31, The imports are using
named imports but the target modules export only default objects; update the
import contracts to import the default and then destructure the needed symbols
(e.g., in .github/scripts/helpers/api.js import the default from './logger.js',
'./validation.js', './constants.js', and './comments.js' and then destructure
getLogger, isSafeSearchToken, requireObject, LABELS, SKILL_HIERARCHY,
ISSUE_STATE, buildBotComment, etc.), and likewise change
.github/scripts/helpers/constants.js to import the default from
'./config-loader.js' and destructure the required config values; alternatively,
change the source modules to export named symbols—whichever you choose, ensure
imports reference default exports (or make corresponding named exports) so
runtime "does not provide an export named" errors are resolved.
There was a problem hiding this comment.
what do you think about this suggestion?
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Export lines in each helper module ==="
for f in .github/scripts/helpers/logger.js \
.github/scripts/helpers/validation.js \
.github/scripts/helpers/constants.js \
.github/scripts/helpers/comments.js \
.github/scripts/helpers/checks.js \
.github/scripts/helpers/config-loader.js; do
echo "--- $f"
grep -n "^export" "$f" 2>/dev/null || echo "(no top-level exports found)"
done
echo ""
echo "=== Named imports in api.js (lines 8-31) ==="
sed -n '1,35p' .github/scripts/helpers/api.js
echo ""
echo "=== Named imports in constants.js ==="
grep -n "^import" .github/scripts/helpers/constants.jsLength of output: 2169
@danielmarv The suggestion is correct and must be fixed — the script confirms every helper module uses only export default { ... }, making all named imports in api.js (and constants.js) broken. They will throw at module link time with something like:
SyntaxError: The requested module './logger.js' does not provide an export named 'getLogger'
This affects:
| File | Broken named imports | Source module's actual export |
|---|---|---|
api.js |
getLogger, isSafeSearchToken, requireObject, … |
export default { at line 50/89/79/207/238 |
api.js |
checkDCO, checkGPG, checkMergeConflict, checkIssueLink |
export default { at line 238 in checks.js |
constants.js |
loadAutomationConfig, buildConstants |
export default { at line 364 in config-loader.js |
Note the original suggestion also missed the checks.js named imports in api.js (lines 24–29) — those need the same fix.
Two consistent ways to fix this:
Option A — Import default + destructure (minimal change, as suggested):
import loggerHelpers from './logger.js';
const { getLogger } = loggerHelpers;
import validationHelpers from './validation.js';
const { isSafeSearchToken, requireObject, requireNonEmptyString, requirePositiveInt, requireSafeUsername } = validationHelpers;
import constants from './constants.js';
const { LABELS, SKILL_HIERARCHY, ISSUE_STATE } = constants;
import checksHelpers from './checks.js';
const { checkDCO, checkGPG, checkMergeConflict, checkIssueLink } = checksHelpers;
import commentHelpers from './comments.js';
const { buildBotComment } = commentHelpers;And in constants.js:
import configLoader from './config-loader.js';
const { loadAutomationConfig, buildConstants } = configLoader;Option B — Switch source modules to named exports (more idiomatic ESM, enables tree-shaking):
Change each helper file from:
export default { getLogger, createLogger, createDelegatingLogger }to:
export { getLogger, createLogger, createDelegatingLogger }…applied consistently across all six helper files. This lets the current import statements in api.js and constants.js work as-is.
Recommendation: Option B is cleaner long-term — it's standard ESM practice and the index.js aggregator can still do import * as logger from './logger.js' or import named symbols directly. Option A works but creates an awkward two-step pattern throughout.
| const { marker, body, allPassed } = buildBotComment({ prAuthor, dco, gpg, merge, issueLink }); | ||
| await postOrUpdateComment(botContext, marker, body); | ||
|
|
||
| return { allPassed }; |
There was a problem hiding this comment.
Don’t ignore dashboard upsert failures in the main PR-check flow.
runAllChecksAndComment awaits postOrUpdateComment but discards its failure result, so status-label transitions can proceed while the dashboard comment was not updated.
💡 Proposed fix
const prAuthor = botContext.pr?.user?.login;
const { marker, body, allPassed } = buildBotComment({ prAuthor, dco, gpg, merge, issueLink });
- await postOrUpdateComment(botContext, marker, body);
+ const commentResult = await postOrUpdateComment(botContext, marker, body);
+ if (!commentResult.success) {
+ throw new Error(`Failed to upsert PR dashboard comment: ${commentResult.error}`);
+ }
return { allPassed };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { marker, body, allPassed } = buildBotComment({ prAuthor, dco, gpg, merge, issueLink }); | |
| await postOrUpdateComment(botContext, marker, body); | |
| return { allPassed }; | |
| const { marker, body, allPassed } = buildBotComment({ prAuthor, dco, gpg, merge, issueLink }); | |
| const commentResult = await postOrUpdateComment(botContext, marker, body); | |
| if (!commentResult.success) { | |
| throw new Error(`Failed to upsert PR dashboard comment: ${commentResult.error}`); | |
| } | |
| return { allPassed }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/helpers/api.js around lines 670 - 673,
runAllChecksAndComment currently awaits postOrUpdateComment but ignores its
success/failure so dashboard upsert failures are dropped; change
runAllChecksAndComment to capture the result/error from postOrUpdateComment
(call to postOrUpdateComment(botContext, marker, body)), verify its returned
success flag or catch and rethrow/provide a failure indicator, and propagate
that failure in the function return (e.g., include an upsertSuccess/upsertError
field or throw) instead of unconditionally returning { allPassed } so
status-label transitions cannot proceed when the dashboard comment update
failed.
| if (!mergeableResolved) { | ||
| logger.log('Merge conflict check: mergeable never resolved after retries, assuming no conflicts'); | ||
| } | ||
| logger.log(`Merge conflict check: ${conflicts ? 'has conflicts' : 'no conflicts'}`); | ||
| return { passed: !conflicts }; | ||
| } |
There was a problem hiding this comment.
Fail-open mergeability handling can incorrectly pass PRs.
Line 118 treats unresolved mergeable as “no conflicts,” and Line 122 returns passed: true. If GitHub never resolves mergeability in time, this can incorrectly advance readiness. Fail closed (or throw so orchestrator marks it as an error) instead.
Suggested fix
- if (!mergeableResolved) {
- logger.log('Merge conflict check: mergeable never resolved after retries, assuming no conflicts');
- }
- logger.log(`Merge conflict check: ${conflicts ? 'has conflicts' : 'no conflicts'}`);
- return { passed: !conflicts };
+ if (!mergeableResolved) {
+ throw new Error('Merge conflict check: mergeable state unresolved after retries');
+ }
+ logger.log(`Merge conflict check: ${conflicts ? 'has conflicts' : 'no conflicts'}`);
+ return { passed: !conflicts };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!mergeableResolved) { | |
| logger.log('Merge conflict check: mergeable never resolved after retries, assuming no conflicts'); | |
| } | |
| logger.log(`Merge conflict check: ${conflicts ? 'has conflicts' : 'no conflicts'}`); | |
| return { passed: !conflicts }; | |
| } | |
| if (!mergeableResolved) { | |
| throw new Error('Merge conflict check: mergeable state unresolved after retries'); | |
| } | |
| logger.log(`Merge conflict check: ${conflicts ? 'has conflicts' : 'no conflicts'}`); | |
| return { passed: !conflicts }; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/helpers/checks.js around lines 118 - 123, The current logic
in the mergeability check treats an unresolved mergeable state as “no conflicts”
(logger.log at the mergeableResolved branch) which can incorrectly pass PRs;
change the behavior in the function containing mergeableResolved/conflicts so
that if mergeableResolved is false you either set conflicts = true or throw an
error instead of assuming no conflicts, update the logger to reflect the
fail-closed decision, and ensure the returned object from the function (the
return { passed: !conflicts } line) will return passed: false (or never return
because you throw) when mergeability never resolved so the orchestrator will
mark it as an error.
| import fs from 'fs'; | ||
| import path from 'path'; | ||
|
|
||
| /** | ||
| * Default path to the repository automation config file. | ||
| * Resolves from helpers/ → scripts/ → .github/hiero-automation.json. | ||
| * @type {string} | ||
| */ | ||
| const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '../../hiero-automation.json'); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Module type declarations =="
fd -HI --exact-depth 4 package.json | while read -r f; do
echo "--- $f"
jq -r '.type // "<no type field>"' "$f" 2>/dev/null || true
done
echo "== Workflow invocation context =="
rg -n --iglob ".github/workflows/*.yml" "bot-on-pr-open\\.js|bot-on-pr-update\\.js|github-script|node "
echo "== ESM/CJS path globals usage =="
rg -n "\\b__dirname\\b|import\\.meta\\.url" .github/scripts/helpers/config-loader.js .github/scripts/*.jsRepository: hiero-ledger/hiero-website
Length of output: 302
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Show config-loader.js lines 1-80 =="
CONFIG_FILE=".github/scripts/helpers/config-loader.js"
if [ -f "$CONFIG_FILE" ]; then
nl -ba "$CONFIG_FILE" | sed -n '1,120p'
else
echo "Missing: $CONFIG_FILE" >&2
fi
echo
echo "== Read root package.json type field =="
if [ -f "package.json" ]; then
jq -r '"type=" + (.type // "null")' package.json 2>/dev/null || cat package.json | head
else
echo "Missing: package.json" >&2
fi
echo
echo "== Find workflow files under .github/workflows (yaml/yml) =="
ls -la .github/workflows || true
fd -t f .github/workflows --extension yml --extension yaml || true
echo
echo "== Search workflows for bot-on-pr-* scripts =="
rg -n --hidden ".github/workflows/.*\\.(ya?ml)$" "bot-on-pr-open\\.js|bot-on-pr-update\\.js|actions/github-script" .github/workflows || true
echo
echo "== Search for __dirname/import.meta.url usage in .github/scripts =="
rg -n "\\b__dirname\\b|import\\.meta\\.url" .github/scripts || true
echo
echo "== Inspect bot entry scripts module syntax =="
for f in .github/scripts/bot-on-pr-open.js .github/scripts/bot-on-pr-update.js; do
if [ -f "$f" ]; then
echo "--- $f"
nl -ba "$f" | sed -n '1,160p'
else
echo "Missing: $f"
fi
doneRepository: hiero-ledger/hiero-website
Length of output: 170
🏁 Script executed:
#!/bin/bash
set -euo pipefail
CONFIG_FILE=".github/scripts/helpers/config-loader.js"
if [ -f "$CONFIG_FILE" ]; then
echo "== Show $CONFIG_FILE (lines 1-120) =="
awk 'NR>=1 && NR<=120 {printf "%6d %s\n", NR, $0}' "$CONFIG_FILE"
else
echo "Missing: $CONFIG_FILE" >&2
fi
echo
echo "== Read root package.json type field =="
if [ -f "package.json" ]; then
if command -v jq >/dev/null 2>&1; then
jq -r '"type=" + (.type // "null")' package.json 2>/dev/null || true
fi
echo "--- snippet ---"
grep -n '"type"' package.json | head -n 20 || true
else
echo "Missing: package.json" >&2
fi
echo
echo "== List .github/workflows =="
ls -la .github/workflows 2>/dev/null || true
echo
echo "== Workflow search for bot scripts =="
rg -n --hidden --no-ignore ".github/workflows" "bot-on-pr-open\\.js|bot-on-pr-update\\.js|actions/github-script" || true
echo
echo "== Search __dirname/import.meta.url usage =="
rg -n --hidden --no-ignore "\\b__dirname\\b|import\\.meta\\.url" .github/scripts || true
echo
echo "== Inspect bot entry scripts =="
for f in .github/scripts/bot-on-pr-open.js .github/scripts/bot-on-pr-update.js; do
if [ -f "$f" ]; then
echo "--- $f (lines 1-200) ---"
awk 'NR>=1 && NR<=200 {printf "%6d %s\n", NR, $0}' "$f"
else
echo "Missing: $f"
fi
doneRepository: hiero-ledger/hiero-website
Length of output: 11063
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for config-loader imports =="
rg -n --hidden --no-ignore "config-loader" .github/scripts || true
echo
echo "== Read .github/scripts/helpers/helpers.js (if present) =="
HELPERS=".github/scripts/helpers/helpers.js"
if [ -f "$HELPERS" ]; then
awk 'NR>=1 && NR<=260 {printf "%6d %s\n", NR, $0}' "$HELPERS"
else
echo "Missing: $HELPERS"
fi
echo
echo "== Inspect workflows for how bot scripts are executed =="
ls -la .github/workflows
for y in .github/workflows/*.yml .github/workflows/*.yaml; do
[ -e "$y" ] || continue
echo "--- $y (lines 1-220) ---"
awk 'NR>=1 && NR<=220 {printf "%6d %s\n", NR, $0}' "$y"
doneRepository: hiero-ledger/hiero-website
Length of output: 7829
Fix ESM __dirname usage in .github/scripts/helpers/config-loader.js to prevent startup-time crashes.
Root package.json sets "type": "module", and config-loader.js is an ESM module (import ...), so const DEFAULT_CONFIG_PATH = path.resolve(__dirname, ...) at line 18 will throw (__dirname is not defined) and block config loading.
💡 Proposed fix
import fs from 'fs';
import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
/**
* Default path to the repository automation config file.
* Resolves from helpers/ → scripts/ → .github/hiero-automation.json.
* `@type` {string}
*/
const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '../../hiero-automation.json');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/scripts/helpers/config-loader.js around lines 10 - 18,
DEFAULT_CONFIG_PATH currently uses __dirname in an ESM module (config-loader.js)
which is undefined; replace the __dirname usage by deriving the directory from
import.meta.url (use fileURLToPath + path.dirname from the 'url' module) and
then compute DEFAULT_CONFIG_PATH with that directory; update the top of the
module to import or require fileURLToPath and derive __dirname-equivalent before
defining DEFAULT_CONFIG_PATH so functions/exports that reference
DEFAULT_CONFIG_PATH (the constant DEFAULT_CONFIG_PATH) no longer throw at
startup.
|
|
||
| jobs: | ||
| on-pr-update: | ||
| runs-on: hiero-client-sdk-linux-large |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether actionlint config exists and how runner labels are declared
fd -HI "actionlint.yaml" .github
fd -HI "actionlint.yml" .github
rg -n "runs-on|hiero-client-sdk-linux-large|self-hosted" .github/workflows/on-pr-update.ymlRepository: hiero-ledger/hiero-website
Length of output: 116
Clarify/whitelist the runner label hiero-client-sdk-linux-large for CI linting.
.github/workflows/on-pr-update.yml uses runs-on: hiero-client-sdk-linux-large, but there’s no actionlint config in .github/ to document/whitelist runner labels—so CI linting can’t reliably resolve self-hosted intent. Ensure actionlint configuration (or equivalent lint rules) and any runner documentation reflect this label.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 20-20: label "hiero-client-sdk-linux-large" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2025-vs2026", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xlarge", "macos-latest-large", "macos-26-intel", "macos-26-xlarge", "macos-26-large", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xlarge", "macos-14-large", "macos-14", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/on-pr-update.yml at line 20, Add an actionlint
configuration entry to whitelist the self-hosted runner label used in
.github/workflows/on-pr-update.yml (the runs-on value
"hiero-client-sdk-linux-large") so actionlint recognizes it as an intended
self-hosted label; update or create the actionlint config (e.g.,
.github/actionlint.yml or .actionlint.yml) to include
"hiero-client-sdk-linux-large" under allowed runner labels and also add a short
note in CI/runner documentation referencing that label for future maintenance.
Source: Linters/SAST tools
| - name: Checkout Repository | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
There was a problem hiding this comment.
Common root cause: credential persistence enabled in privileged pull_request_target workflows (.github/workflows/on-pr.yml, .github/workflows/on-pr-update.yml).
actions/checkout should set persist-credentials: false in both files to reduce write-token exposure across steps.
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 35-36: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/on-pr.yml around lines 35 - 36, The checkout step
currently uses actions/checkout without disabling credential persistence; update
the Checkout Repository step (the actions/checkout@... usage) in both
.github/workflows/on-pr.yml and .github/workflows/on-pr-update.yml to include
persist-credentials: false so the checkout action does not persist the
GITHUB_TOKEN across subsequent steps in the privileged pull_request_target
workflow.
Source: Linters/SAST tools
| const script = require('./.github/scripts/bot-on-pr-open.js'); | ||
| await script({ github, context }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate workflow files
ls -la .github/workflows | sed -n '1,200p'
# Show relevant snippets for on-pr.yml around lines 35-60
echo "---- on-pr.yml (lines 30-70) ----"
nl -ba .github/workflows/on-pr.yml | sed -n '30,80p'
# Show relevant snippets for on-pr-update.yml around lines 30-80 if exists
if [ -f .github/workflows/on-pr-update.yml ]; then
echo "---- on-pr-update.yml (lines 30-90) ----"
nl -ba .github/workflows/on-pr-update.yml | sed -n '30,110p'
else
echo "Missing .github/workflows/on-pr-update.yml"
fi
# List bot scripts referenced
echo "---- bot scripts in .github/scripts (filtered) ----"
ls -ლა .github/scripts | sed -n '1,200p' || true
for f in .github/scripts/bot-on-pr-open.js .github/scripts/bot-on-pr-update.js; do
if [ -f "$f" ]; then
echo "---- $f (head 120) ----"
nl -ba "$f" | sed -n '1,140p'
else
echo "Missing $f"
fi
done
# Check package.json type/module for ESM resolution
if [ -f package.json ]; then
echo "---- package.json (type field) ----"
node -p "JSON.parse(require('fs').readFileSync('package.json','utf8')).type || 'none'" 2>/dev/null || true
cat package.json | sed -n '1,120p'
else
echo "No package.json found"
fiRepository: hiero-ledger/hiero-website
Length of output: 526
🏁 Script executed:
#!/bin/bash
set -euo pipefail
show_with_lines () {
local file="$1"
local from="$2"
local to="$3"
python3 - <<PY
import itertools,sys
path=sys.argv[1]
a=int(sys.argv[2]); b=int(sys.argv[3])
with open(path,'r',encoding='utf-8') as f:
for i,line in enumerate(f, start=1):
if a <= i <= b:
sys.stdout.write(f"{i:5d}: {line}")
PY
"$file" "$from" "$to"
}
echo "---- workflow files ----"
ls -ლა .github/workflows
echo "---- on-pr.yml (lines 35-90) ----"
show_with_lines ".github/workflows/on-pr.yml" 35 90
echo "---- on-pr-update.yml (lines 35-120) ----"
show_with_lines ".github/workflows/on-pr-update.yml" 35 120
echo "---- grep require usage in those workflows ----"
rg -n "require\\(\\s*['\"]\\.\\/\\.github\\/scripts\\/bot-on-pr-" .github/workflows/on-pr.yml .github/workflows/on-pr-update.yml || true
rg -n "require\\(" .github/workflows/on-pr.yml .github/workflows/on-pr-update.yml || true
echo "---- bot scripts content (first 200 lines each) ----"
for f in .github/scripts/bot-on-pr-open.js .github/scripts/bot-on-pr-update.js; do
if [ -f "$f" ]; then
echo "===== $f ====="
show_with_lines "$f" 1 220
else
echo "Missing $f"
fi
done
echo "---- detect module format in bot scripts ----"
for f in .github/scripts/bot-on-pr-open.js .github/scripts/bot-on-pr-update.js; do
if [ -f "$f" ]; then
echo "=== $f: export/module.exports/require presence ==="
rg -n "(^|\\s)export\\s|export\\s+default|module\\.exports\\s*=|module\\.exports\\s*\\.|require\\s*\\(" "$f" || true
fi
done
echo "---- package.json (type field) ----"
if [ -f package.json ]; then
node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); console.log(p.type||'none')" || true
sed -n '1,120p' package.json
else
echo "No package.json found"
fiRepository: hiero-ledger/hiero-website
Length of output: 177
🏁 Script executed:
#!/bin/bash
set -euo pipefail
show_with_lines () {
python3 - "$1" "$2" "$3" <<'PY'
import sys
path, a, b = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
with open(path,'r',encoding='utf-8') as f:
for i,line in enumerate(f, start=1):
if a <= i <= b:
sys.stdout.write(f"{i:5d}: {line}")
PY
}
echo "---- workflow files ----"
ls -la .github/workflows || true
echo "---- on-pr.yml (lines 1-140) ----"
show_with_lines ".github/workflows/on-pr.yml" 1 140
echo "---- on-pr-update.yml (lines 1-200) ----"
show_with_lines ".github/workflows/on-pr-update.yml" 1 200
echo "---- grep require usage in workflows ----"
rg -n "require\\(" .github/workflows/on-pr.yml .github/workflows/on-pr-update.yml || true
echo "---- bot scripts existence ----"
ls -la .github/scripts || true
for f in .github/scripts/bot-on-pr-open.js .github/scripts/bot-on-pr-update.js; do
echo "---- $f (1-220) ----"
if [ -f "$f" ]; then
show_with_lines "$f" 1 220
echo "---- module format signals in $f ----"
rg -n "(^|\\s)export\\s+default|(^|\\s)export\\s+\\{|module\\.exports\\s*=|module\\.exports\\s*\\.|require\\s*\\(" "$f" || true
else
echo "Missing $f"
fi
done
echo "---- package.json (type) ----"
if [ -f package.json ]; then
node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));console.log('type:',p.type||'none')" || true
sed -n '1,120p' package.json
else
echo "No package.json found"
fiRepository: hiero-ledger/hiero-website
Length of output: 10626
Fix ESM bot entrypoint loading in actions/github-script (replace require with import + .default)
.github/workflows/on-pr.yml and .github/workflows/on-pr-update.yml use require('./.github/scripts/bot-on-pr-*.js'), but the bot entrypoints are ESM (package.json has "type": "module" and both scripts use export default). Switch to dynamic import() and invoke the default export:
const mod = await import('./.github/scripts/bot-on-pr-open.js'); // or bot-on-pr-update.js
await mod.default({ github, context });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/on-pr.yml around lines 42 - 43, Replace the CommonJS
require usage that loads the ESM bot entrypoint (the line using
require('./.github/scripts/bot-on-pr-open.js') and the subsequent await script({
github, context })) with a dynamic import and invoke the module's default
export: use await import('./.github/scripts/bot-on-pr-open.js') to get the
module object and then call its default export with the same arguments (e.g.,
await mod.default({ github, context })), ensuring the ESM export default is
executed correctly.
Description
This PR adds a GitHub-action to present contributors with a PR dashboard that allows the to see the status of their commits. This dashboard updates on commit pushes ensuring that each PR meets the proper requirements for allowing commit signing and merging.
Changes Made
Related Issues
Fixes: #499
Screenshots (if applicable)
Checklist
Deployment Notes
Links to testing:
Summary by CodeRabbit